$$ \newcommand{\floor}[1]{\left\lfloor{#1}\right\rfloor} \newcommand{\ceil}[1]{\left\lceil{#1}\right\rceil} \renewcommand{\mod}{\,\mathrm{mod}\,} \renewcommand{\div}{\,\mathrm{div}\,} \newcommand{\metar}{\,\mathrm{m}} \newcommand{\cm}{\,\mathrm{cm}} \newcommand{\dm}{\,\mathrm{dm}} \newcommand{\litar}{\,\mathrm{l}} \newcommand{\km}{\,\mathrm{km}} \newcommand{\s}{\,\mathrm{s}} \newcommand{\h}{\,\mathrm{h}} \newcommand{\minut}{\,\mathrm{min}} \newcommand{\kmh}{\,\mathrm{\frac{km}{h}}} \newcommand{\ms}{\,\mathrm{\frac{m}{s}}} \newcommand{\mss}{\,\mathrm{\frac{m}{s^2}}} \newcommand{\mmin}{\,\mathrm{\frac{m}{min}}} \newcommand{\smin}{\,\mathrm{\frac{s}{min}}} $$

Prijavi problem


Obeleži sve kategorije koje odgovaraju problemu

Još detalja - opišite nam problem


Uspešno ste prijavili problem!
Status problema i sve dodatne informacije možete pratiti klikom na link.
Nažalost nismo trenutno u mogućnosti da obradimo vaš zahtev.
Molimo vas da pokušate kasnije.

Израда цртежа помоћу петљи

Размотримо следећи задатак: нека је потребно нацртати 6 кругова, као на овој слици:

../_images/drawing_loops_target.png

Гледајући у слику можемо да претпоставимо (а могло је да буде и речено у поставци) да су кругови једнако размакнути. Ово значи да је разлика полупречника свака два суседна круга иста.

Величину кругова бирамо тако да буду што већи, али да могу да стану у дати простор за цртање од 300x300 пиксела. Пошто је ширина прозора 300 пиксела, пречник највећег круга је 300. Као разлику пречника два суседна круга можемо да узмемо \({300 \over 6} = 50\). Тако добијамо пречнике 50, 100, 150, 200, 250, 300. Слично добијамо и горња лева темена описаних квадрата.

На основу израчунатих вредности, могли бисмо да напишемо овакву функцију за цртање:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Pen olovka = new Pen(Color.Red, 2);
    g.DrawEllipse(olovka, 125, 125, 50, 50);
    g.DrawEllipse(olovka, 100, 100, 100, 100);
    g.DrawEllipse(olovka, 75, 75, 150, 150);
    g.DrawEllipse(olovka, 50, 50, 200, 200);
    g.DrawEllipse(olovka, 25, 25, 250, 250);
    g.DrawEllipse(olovka, 0, 0, 300, 300);
}

Замислимо да смо после овога добили нови задатак да направимо исти такав цртеж, али са 5 кругова. Ово је врло мала промена, зар не? Требало би да можемо да искористимо нешто од претходно решеног задатка.

Када започнемо рад на цртежу од 5 кругова, видимо да врло мало од претходног програма можемо да искористимо. У ствари, можемо да искористимо само идеју, а величине кругова треба да израчунамо испочетка.

Да смо програм писали другачије, прилагођавање би било много једноставније. Могли смо на пример да број кругова упишемо у променљиву, а затим да у свим потребним рачунањима користимо ту промељиву. Та функција Form1_Paint би изгледала овако:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Pen olovka = new Pen(Color.Red, 2);
    int brKrugova = 6;
    int cx = ClientSize.Width / 2;
    int cy = ClientSize.Height / 2;
    float rKorak = ClientSize.Width / (2.0f * brKrugova);
    for (int i = 1; i <= brKrugova; i++)
    {
        int r = (int)(0.5f + i * rKorak);
        g.DrawEllipse(olovka, cx - r, cy - r, 2 * r, 2 * r);
    }
}

У овом програму је довољно да се измени само један број, па да он црта било који задати број кругова.

Као што смо већ поменули, код многих цртежа постоји нека правилност, као што је симетрија или неки део који се понавља (и многе друге, сложеније правилности). Ако схватимо правилност на таквим цртежима и изразимо је математички, моћи ћемо да је искористимо при писању програма за цртање таквих цртежа, као што смо урадили у претходном примеру. На тај начин добијамо програм који је много лакше изменити да би се добио неки други, сличан цртеж. Код цртежа са великим бројем понављања неког дела (истоветног или мало измењеног), програм који користи правилност ће бити и доста краћи, а тиме и читљивији.

Многи програми које користе милиони људи се стално усавршавају и дорађују. Стално се објављују нове верзије у којима је нешто урађено боље. Према томе, измене програма су нешто потпуно нормално и нешто што се стално дешава. Ситуација је слична и са програмима које сами пишемо. Када напишемо неки програм, лако може да се догоди да се касније сетимо нечега, због чега ћемо хтети да изменимо део програма који је већ написан.

Зато, када пишемо програме, треба да имамо на уму могућност да ће неко (могуће и ми сами) хтети да направи неки сличан програм и да ће можда желети да употреби наш програм као почетну верзију.


Када смо већ на овај начин написали функцију за цртање кругова, сада више нисмо далеко ни од прогама у коме корисник може да зада број кругова које жели да буду нацртани. Ради тога ћемо на формулар поставити једно нумеричко поље и дати му име nBrojKrugova. Наравно, потребно је и у функцији Form1_Paint наредбу

int brKrugova = 6;

заменити наредбом

int brKrugova = (int)nBrojKrugova.Value;

Ипак, када покренемо овако написан програм, број кругова на слици се (још увек) не мења. То је зато што програм „не зна” да треба поново да изврши функцију Form1_Paint. Овај недостатак ћемо лако исправити тако што испрограмирамо догађај ValueChanged нумеричког поља на следећи начин:

private void nBrojKrugova_ValueChanged(object sender, EventArgs e)
{
    Invalidate();
}

Извршавањем функције Invalidate ми у суштини кажемо рачунару: „Изглед овог прозора није више валидан. Кад будеш имао времена, покрени функцију Form1_Paint да би прозор био нацртан како треба”.

Напомена: функција Refresh() је слична функцији Invalidate(). Она такође проузрокује да прозор буде поново нацртан коришћењем функције Form1_Paint, па ћете можда виђати у програмима да се уместо функције Invalidate() користи функција Refresh().

Разлика између ових функција је у томе што Refresh() инсистира да се потребно исцртавање догоди одмах. У многим случајевима нам је свеједно да ли ће се цртање десити одмах или „кад буде времена за то”. У ствари, код једноставних примера се најчешће веома брзо нађе времена за поновно цртање прозора, тако да не можемо ни да приметимо неку разлику.

Међутим, код сложених цртања која треба често да се понављају, позивањем Refresh() се инсистира на томе да се свако цртање обави одмах. То може проузроковати да се рачунар загуши захтевима и да програм престане да реагује на акције корисника док се сва тражена цртања не обаве. Ако захтеви за цртањем пристижу брже него што се разрешавају, ово загушење може и да потраје.

Са друге стране, ако више пута позовемо Invalidate() пре него што се исцртавање обави, при првом следећем цртању прозора (извршавању функције Form1_Paint) сви претходни захтеви за ажурирањем прозора су разрешени. Тиме неке међу-слике можда неће ни бити нацртане (што такође често пролази неопажено, нарочито када су промене на слици веома мале), али програм ће бити мање затрпан захтевима за цртањем и моћи ће нормално да реагује на акције корисника.

Укратко, функција Refresh() је нешто „себичнија” (нацртај ми сваку слику одмах), док је функција Invalidate() „љубазнија” према рачунару (цртај кад стигнеш, а ако баш не стижеш - прескочи нешто). У примерима где је портебно поновити исцртавање, ми ћемо користити функцију Invalidate().


Погледајмо још неке примере употребе петљи при цртању ради добијања краћих и флексибилнијих програма (програма који је лакше прилагодити мало другачијој намени).

Пример - антена

Пре кабловске телевизије телевизијски сигнал се примао помоћу антена које су људи углавном постављали на кровове својих кућа и зграда. У овом примеру видећемо како да нацртамо једну такву антену.

../_images/drawing_loops_antenna.png

Треба нацртати укупно 7 дужи. Усправна дуж (осовина антене) је дебљине 4 пиксела, а од 6 водоравних дужи (попречни сегменти) две дуже су дебљиве 3, две средње су дебљиве 2, а две краће дебљине 1. Антену можемо да нацртамо на пример овако:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.DrawLine(new Pen(Color.Black, 4), 150, 50, 150, 250); // osovina

    double debljina = 1.0;
    int x1 = 120, x2 = 180, y = 75;
    for (int i = 0; i < 6; i++)
    {
        g.DrawLine(new Pen(Color.Black, (int)debljina), x1, y, x2, y);
        x1 -= 10; // poprecni segment se produzava s leva za 10 ...
        x2 += 10; // ... i s desna za 10,
        y += 25;  // a nalazi se 25 piksela nize od prethodnog
        debljina += 0.5;
    }
}

или овако

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    g.DrawLine(new Pen(Color.Black, 4), 150, 50, 150, 250); // osovina
    for (int i = 0; i < 6; i++)
        g.DrawLine(new Pen(Color.Black, 1 + i / 2),
            120 - 10 * i, 75 + 25 * i,
            180 + 10 * i, 75 + 25 * i);
}

или на неки сличан начин.

Задатак: Напишите свој програм који црта овакву антену. Настојте да напишете програм тако да слике разних сличних антена можете да добијете са што мање измена у програму.

../_images/drawing_loops_antenna_a.png ../_images/drawing_loops_antenna_b.png ../_images/drawing_loops_antenna_c.png

Генерисање аритметичких прогресија

Аритметичка прогресија је такав низ бројева у коме се свака два узастопна броја разликују за исту величину. На пример, низ y координата попречних сегмената антене у претходном примеру чини аритметичку прогресију 75, 100, 125, 150, 175, 200. Бројеве који чине аритметичку прогресију можемо да формирамо на више начина помоћу for петље. На пример

for (int i = 0; i < 6; i++)
{
    y = 75 + 25 * i;
    ...
}

или

for (int y = 75; y <= 200; y += 25)
{
    ...
}

У општем случају, ако треба добити серију вредности \(a,~ a+d,~ a+2d, \cdots ~ a+(n-1)d\), било која од следећих петљи ће послужити:

for (int i = 0; i < n; i++)
{
    y = a + d * i;
    ...
}
int y = a;
for (int i = 0; i < n; i++)
{
    ...
    y += d;
}
for (int y = a; y < a + n * d; y += d)
{
    ...
}

У наредним задацима ће нам често бити потребне петље овог облика.

У задатку са антеном појавило се неколико аритметичких прогресија од по 6 елемената. Осим y координата попречних сегмената, аритметичке прогресије чине бар још и x координате левих крајева (120, 110, 100, 90, 80, 70), као и x координате десних крајева (180, 190, 200, 210, 220, 230). У таквим ситуацијама последњи начин формирања петље (for (y…)) није најпогоднији, па смо тада као примере навели само прва два начина.

Следећа питања ће вам помоћи да утврдите знање о формирању аритметичких прогресија у програмима.

Питања

    Q-22: Која наредба додељивања треба да стоји у петљи да би x добијало редом вредности 100, 150, 200, 250, 300, ако i пролази скуп (0, 1, 2, 3, 4)?

  • x = 100 + i*50
  • Тачно!
  • x = 50 + i*100
  • Не.
  • x = i*100
  • Не.
  • x = 100+i*100
  • Не.

    Q-23: Који израз треба користити у петљи

    for (int i = 0; i < 19; i++)
    {
        x = ???
        ...
    }
    

    да би x узимало исте вредности као у петљи

    for (int x = 25; x < 500; x += 50)
    {
        ...
    }
    
  • x = 25 * i + 50
  • Не.
  • x = (25 + i) * 50
  • Не.
  • x = 25 * 2*i+1
  • Не.
  • x = 25 + 50 * i
  • Тачно!

Задатак - дрворед

Следећа функција приказује дрворед. Измените je тако да се што једноставније преправља и црта било који други број стабала. Идеално, у програму треба изменити само један број, а затим положаји стабала израчунавају на основу тог броја.

Додатно, можете да покушате да и величину стабала прилагодите њиховом броју (ако их је више да буду мања).

../_images/drawing_loops_trees.png
private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Brush zelenaCetka = new SolidBrush(Color.DarkGreen);
    Brush braonCetka = new SolidBrush(Color.Brown);

    int yHorizont = ClientSize.Height * 3 / 4;
    g.FillRectangle(zelenaCetka, 0, yHorizont, ClientSize.Width, ClientSize.Height - yHorizont); // trava

    for (int i = 0; i < 6; i++)
    {
        g.FillRectangle(braonCetka, 100 * i + 40, 180, 20, 100); // stablo
        g.FillEllipse(zelenaCetka, 100 * i + 10, 50, 80, 150);   // krosnja
    }
}

Задатак - жирафе

Напишите функцију CrtajMnogougao којој се може задати низ темена многоугла, његова боја, положај и величина, а која онда израчунава темена сличног многоугла на задатом месту и задате величине и црта тај нови многоугао.

Програм са таквом функцијом CrtajMnogougao и функцијом Form1_Paint датом у наставку треба да нацрта овакву слику:

../_images/drawing_loops_giraffe_herd.png
private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Point[] zirafa = new Point[] {
        new Point(40, 208), new Point(40, 107), new Point(88, 82), new Point(134, 13),
        new Point(128, 9), new Point(134, 13), new Point(137, 11), new Point(128, 6),
        new Point(160, 25), new Point(159, 28), new Point(136, 28), new Point(98, 101),
        new Point(100, 106), new Point(101, 207), new Point(97, 207), new Point(95, 164),
        new Point(83, 121), new Point(85, 128), new Point(54, 128), new Point(55, 119),
        new Point(44, 165), new Point(44, 208)
    };

    Point[] poz = new Point[] { new Point(30, 35), new Point(0, 0),
        new Point(100, 10), new Point(50, 65), new Point(80, 130) };

    float[] velicina = new float[] { 0.4f, 1.1f, 0.7f, 0.9f, 0.8f };

    Color[] boja = new Color[] { Color.DarkKhaki, Color.Khaki,
        Color.DarkKhaki, Color.Peru, Color.DarkGoldenrod };

    for (int i = 0; i < 4; i++)
        CrtajMnogougao(g, zirafa, boja[i], poz[i].X, poz[i].Y, velicina[i]);

}

Задатак - стрелице

Напишите програм који приказује слику што сличнију овој:

../_images/drawing_loops_arrows.png

Задатак - Решетка

Измените следећу функцију за цртање тако да се у једној петљи исртавају усправне линије, а након тога у другој петљи водоравне линије.

Пре испробавања величину формулара прилагодите величини цртежа.

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Pen olovka = new Pen(Color.Black, 1);

    g.DrawLine(olovka, 10, 10, 10, ClientSize.Height - 10);
    g.DrawLine(olovka, 30, 10, 30, ClientSize.Height - 10);
    g.DrawLine(olovka, 50, 10, 50, ClientSize.Height - 10);
    g.DrawLine(olovka, 70, 10, 70, ClientSize.Height - 10);
    g.DrawLine(olovka, 90, 10, 90, ClientSize.Height - 10);
    g.DrawLine(olovka, 110, 10, 110, ClientSize.Height - 10);
    g.DrawLine(olovka, 130, 10, 130, ClientSize.Height - 10);
    g.DrawLine(olovka, 150, 10, 150, ClientSize.Height - 10);
    g.DrawLine(olovka, 170, 10, 170, ClientSize.Height - 10);
    g.DrawLine(olovka, 190, 10, 190, ClientSize.Height - 10);
    g.DrawLine(olovka, 210, 10, 210, ClientSize.Height - 10);
    g.DrawLine(olovka, 230, 10, 230, ClientSize.Height - 10);
    g.DrawLine(olovka, 250, 10, 250, ClientSize.Height - 10);
    g.DrawLine(olovka, 270, 10, 270, ClientSize.Height - 10);
    g.DrawLine(olovka, 290, 10, 290, ClientSize.Height - 10);
    g.DrawLine(olovka, 310, 10, 310, ClientSize.Height - 10);
    g.DrawLine(olovka, 330, 10, 330, ClientSize.Height - 10);
    g.DrawLine(olovka, 350, 10, 350, ClientSize.Height - 10);
    g.DrawLine(olovka, 370, 10, 370, ClientSize.Height - 10);
    g.DrawLine(olovka, 390, 10, 390, ClientSize.Height - 10);
    g.DrawLine(olovka, 410, 10, 410, ClientSize.Height - 10);
    g.DrawLine(olovka, 430, 10, 430, ClientSize.Height - 10);
    g.DrawLine(olovka, 450, 10, 450, ClientSize.Height - 10);
    g.DrawLine(olovka, 470, 10, 470, ClientSize.Height - 10);
    g.DrawLine(olovka, 490, 10, 490, ClientSize.Height - 10);

    g.DrawLine(olovka, 10, 10, ClientSize.Width - 10, 10);
    g.DrawLine(olovka, 10, 30, ClientSize.Width - 10, 30);
    g.DrawLine(olovka, 10, 50, ClientSize.Width - 10, 50);
    g.DrawLine(olovka, 10, 70, ClientSize.Width - 10, 70);
    g.DrawLine(olovka, 10, 90, ClientSize.Width - 10, 90);
    g.DrawLine(olovka, 10, 110, ClientSize.Width - 10, 110);
    g.DrawLine(olovka, 10, 130, ClientSize.Width - 10, 130);
    g.DrawLine(olovka, 10, 150, ClientSize.Width - 10, 150);
    g.DrawLine(olovka, 10, 170, ClientSize.Width - 10, 170);
    g.DrawLine(olovka, 10, 190, ClientSize.Width - 10, 190);
    g.DrawLine(olovka, 10, 210, ClientSize.Width - 10, 210);
    g.DrawLine(olovka, 10, 230, ClientSize.Width - 10, 230);
    g.DrawLine(olovka, 10, 250, ClientSize.Width - 10, 250);
    g.DrawLine(olovka, 10, 270, ClientSize.Width - 10, 270);
    g.DrawLine(olovka, 10, 290, ClientSize.Width - 10, 290);
}